package com.umeox.stopwatch

import java.util.concurrent.TimeUnit

class Stopwatch {
    var isRunning: Boolean = false
    private var elapsedNanos: Long = 0
    private var startTick: Long = 0
    private val ticker: Ticker

    internal constructor() {
        ticker = Ticker.systemTicker()
    }

    internal constructor(ticker2: Ticker) {
        ticker = ticker2
    }


    fun start(): Stopwatch {
        isRunning = true
        startTick = ticker.read()
        return this
    }


    fun pause(): Stopwatch {
        if (!isRunning) return this
        val tick = ticker.read()
        isRunning = false
        elapsedNanos += tick - startTick
        return this
    }


    fun stop(): Stopwatch {
        elapsedNanos = 0
        isRunning = false
        return this
    }

    private fun elapsedNanos(): Long {
        return if (isRunning) (ticker.read() - startTick) + elapsedNanos else elapsedNanos
    }

    fun elapsed(desiredUnit: TimeUnit): Long {
        return desiredUnit.convert(elapsedNanos(), TimeUnit.NANOSECONDS)
    }

    override fun toString(): String {
        val nanos = elapsedNanos()
        val unit = chooseUnit(nanos)
        return TimeUnit.NANOSECONDS.toMillis(nanos).toString() + " " + abbreviate(unit)
    }

    companion object {
        @JvmStatic
        fun createUnstarted(): Stopwatch {
            return Stopwatch()
        }

        fun createUnstarted(ticker2: Ticker): Stopwatch {
            return Stopwatch(ticker2)
        }

        fun createStarted(): Stopwatch {
            return Stopwatch().start()
        }

        fun createStarted(ticker2: Ticker): Stopwatch {
            return Stopwatch(ticker2).start()
        }

        private fun chooseUnit(nanos: Long): TimeUnit {
            if (TimeUnit.DAYS.convert(nanos, TimeUnit.NANOSECONDS) > 0) {
                return TimeUnit.DAYS
            }
            if (TimeUnit.HOURS.convert(nanos, TimeUnit.NANOSECONDS) > 0) {
                return TimeUnit.HOURS
            }
            if (TimeUnit.MINUTES.convert(nanos, TimeUnit.NANOSECONDS) > 0) {
                return TimeUnit.MINUTES
            }
            if (TimeUnit.SECONDS.convert(nanos, TimeUnit.NANOSECONDS) > 0) {
                return TimeUnit.SECONDS
            }
            if (TimeUnit.MILLISECONDS.convert(nanos, TimeUnit.NANOSECONDS) > 0) {
                return TimeUnit.MILLISECONDS
            }
            if (TimeUnit.MICROSECONDS.convert(nanos, TimeUnit.NANOSECONDS) > 0) {
                return TimeUnit.MICROSECONDS
            }
            return TimeUnit.NANOSECONDS
        }

        private fun abbreviate(unit: TimeUnit): String {
            return when (unit) {
                TimeUnit.NANOSECONDS -> "ns"
                TimeUnit.MICROSECONDS -> "\u03bcs" // μs
                TimeUnit.MILLISECONDS -> "ms"
                TimeUnit.SECONDS -> "s"
                TimeUnit.MINUTES -> "min"
                TimeUnit.HOURS -> "h"
                TimeUnit.DAYS -> "d"
                else -> throw AssertionError()
            }
        }
    }
}
